import { CanvasCtrl } from "./CanvasCtrl";

export interface IDot {
  x: number;
  y: number;
  name?: string;
}
const { abs, sqrt } = Math;
const { EPSILON: eps } = Number
const area = (a: IDot, b: IDot, c: IDot) => {
  const x1 = b.x - a.x;
  const y1 = b.y - a.y;
  const x2 = c.x - a.x;
  const y2 = c.y - a.y;
  return abs((x1 * y2 - x2 * y1) / 2);
}
const triangle_area = (a: IDot, b: IDot, c: IDot) => {
  const x = sqrt(abs(a.x - b.x) * abs(a.x - b.x) + abs(a.y - b.y) * abs(a.y - b.y));
  const y = sqrt(abs(a.x - c.x) * abs(a.x - c.x) + abs(a.y - c.y) * abs(a.y - c.y));
  const z = sqrt(abs(c.x - b.x) * abs(c.x - b.x) + abs(c.y - b.y) * abs(c.y - b.y));
  const d = (x + y + z) / 2;
  return sqrt(abs(d * (d - x) * (d - y) * (d - z)));
}
const dot_in_triangle = (a: IDot, b: IDot, c: IDot, p: IDot) => {
  const area0 = area(a, b, c);
  const area1 = area(a, b, p);
  const area2 = area(a, c, p);
  const area3 = area(b, c, p);
  const sumarea = area1 + area2 + area3;
  if (abs(area0 - sumarea) >= eps)
    return 0
  return (
    abs(area1) < eps ||
    abs(area2) < eps ||
    abs(area3) < eps
  ) ? 2 : 1
}
export const dot_in_circle = (o: IDot, r: number, p: IDot) => {
  const d = (p.x - o.x) * (p.x - o.x) + (p.y - o.y) * (p.y - o.y)
  const l = d - r * r
  if (l > eps) return 0;
  return l < -eps ? 1 : 2
}
const cross_dot = (a: IDot, b: IDot, c: IDot, d: IDot) => {
  const a1 = d.y - c.y;
  const a2 = c.y - a.y;
  const a3 = b.y - a.y;
  const b1 = d.x - c.x;
  const b2 = c.x - a.x;
  const b3 = b.x - a.x;
  const t = (a3 * b2 - a2 * b3) / (a1 * b3 - a3 * b1);
  return [
    (d.x - c.x) * t + c.x,
    (d.y - c.y) * t + c.y
  ]
}
const random_between = (min: number, max: number) => min + (max - min) * Math.random()

export const line_circle_intersection = (ox: number, oy: number, r: number, ax: number, ay: number, bx: number, by: number): [number, number][] => {
  const dx = bx - ax;
  const dy = by - ay;
  const a = dx * dx + dy * dy;
  const b = 2 * (dx * (ax - ox) + dy * (ay - oy));
  const c = (ax - ox) * (ax - ox) + (ay - oy) * (ay - oy) - r * r;
  const d = b * b - 4 * a * c;

  if (d < -Number.EPSILON)
    return [];
  if (d < Number.EPSILON) {
    const t = -b / (2 * a);
    return [[ax + t * dx, ay + t * dy]];
  }
  const t1 = (-b + Math.sqrt(d)) / (2 * a);
  const t2 = (-b - Math.sqrt(d)) / (2 * a);
  return [
    [ax + t1 * dx, ay + t1 * dy],
    [ax + t2 * dx, ay + t2 * dy]
  ]

}
export const manhattan = (a: number, b: number, c: number, d: number) => {
  const { abs } = Math;
  return abs(a - c) + abs(b - d)
}
export function dot(x: number, y: number, name?: string): IDot {
  return { x, y, name }
}
export class Ctrl {
  tri: TriCir;
  dot: IDot
  watch: IDot
  radius = 10;
  readonly name: string;
  constructor(tri: TriCir, dot: IDot, watch: IDot, name: string) {
    this.tri = tri
    this.dot = { ...dot }
    this.watch = { ...watch }
    this.name = name
  }
  set_dot(x: number, y: number) {
    this.dot.x = x;
    this.dot.y = y;
  }
  set_watch(x: number, y: number) {
    this.watch.x = x;
    this.watch.y = y;
  }
}

export class TriCir<C extends Ctrl = Ctrl> {
  shadow_dots: IDot[] = [];
  w: number = 0;
  h: number = 0;
  o = dot(0, 0)
  r: number = 0;
  situations: string[] = [];
  ctrls: C[] = []

  constructor(w: number, h: number, C: new (...args: ConstructorParameters<typeof Ctrl>) => C) {
    this.ctrls.push(
      new C(this, dot(0, 0, 'ctrl_a'), dot(0, 0, 'watch_a'), 'A'),
      new C(this, dot(0, 0, 'ctrl_b'), dot(0, 0, 'watch_b'), 'B'),
      new C(this, dot(0, 0, 'ctrl_c'), dot(0, 0, 'watch_c'), 'C'),
    )
    this.set_size(w, h)


  }
  cur_situations() {
    const { shadow_dots } = this;
    const cross_dots = this.cross_dots()
    const cross_tri_name = cross_dots.map(v => v.name).sort().join('')
    const shadown_tri_name = shadow_dots.map(v => v.name).sort().join('')
    const situations: string[] = [];
    for (const shadow_dot of shadow_dots) {
      const hit = dot_in_triangle(cross_dots[0], cross_dots[1], cross_dots[2], shadow_dot)
      situations.push(`点${shadow_dot.name}${hit ? '在' : '不在'}三角形${cross_tri_name}中`)
    }
    for (const cross_dot of cross_dots) {
      const hit1 = dot_in_triangle(shadow_dots[0], shadow_dots[1], shadow_dots[2], cross_dot)
      situations.push(`点${cross_dot.name}${hit1 ? '在' : '不在'}三角形${shadown_tri_name}中`)
      const hit2 = dot_in_circle(this.o, this.r, cross_dot)
      situations.push(`点${cross_dot.name}${hit2 ? '在' : '不在'}虚线圆中`)
    }
    return situations.sort()
  }
  gen_situations() {
    this.random_ctrls(true)
    this.shadow_dots = this.cross_dots()
    const x = 'X'.charCodeAt(0)
    this.shadow_dots.forEach((dot, i) => dot.name = String.fromCharCode(x + i))
    this.random_ctrls(false)
    this.situations = ['target:', ...this.cur_situations().map(v => '请确保' + v)]

    this.random_ctrls(false)
  }
  set_size(w: number, h: number) {
    this.w = w;
    this.h = h;
    this.o.x = w / 2;
    this.o.y = h / 2;
    this.r = Math.min(w / 2, h / 2) - 20;
    this.init_ctrls()
    this.gen_situations()
  }

  make_shadow(nums: number[]) {
    const len = nums.length
    for (let i = 0; i < len; i += 2) {
      this.ctrls[i].set_dot(nums[i], nums[i + 1]);
    }
  }

  init_ctrls() {
    const { o, r } = this;
    const len = this.ctrls.length
    const d = Math.PI * 2 / len;
    this.ctrls.forEach((ctrl, i,) => {
      ctrl.set_watch(
        o.x + r * Math.sin((i + 2) % len * d),
        o.y + r * Math.cos((i + 2) % len * d)
      )
    })
  }

  random_ctrls(safe: boolean) {
    const { o, r } = this;
    const d = Math.PI * 2 / this.ctrls.length;
    this.ctrls.forEach((ctrl, i) => {
      const a = safe ? random_between(d * i, d * i + d / 2) : random_between(0, Math.PI * 2)
      ctrl.set_dot(o.x + r * Math.sin(a), o.y + r * Math.cos(a));
    })
  }

  cross_dots(): IDot[] {
    const a_code = 'A'.charCodeAt(0);
    return this.ctrls.map((curr, i, l) => {
      const next = this.ctrls[(i + 1) % l.length]
      const name = String.fromCharCode(a_code + next.name.charCodeAt(0) - a_code)
      const [x, y] = cross_dot(
        curr.dot,
        curr.watch,
        next.dot,
        next.watch
      )
      return dot(x, y, name)
    })
  }
}

